package ch.prokopovi.ui.main;

import java.text.DecimalFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.ShareActionProvider;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import ch.prokopovi.PrefsUtil;
import ch.prokopovi.R;
import ch.prokopovi.StatsHelper;
import ch.prokopovi.VersionHelper;
import ch.prokopovi.api.struct.BestRatePlace;
import ch.prokopovi.db.BestRatesDbAdapter;
import ch.prokopovi.db.DbHelper;
import ch.prokopovi.struct.Master.CurrencyCode;
import ch.prokopovi.struct.Master.OperationType;
import ch.prokopovi.struct.Master.Region;
import ch.prokopovi.ui.main.RateAppFragment.RateAppListener;
import ch.prokopovi.ui.main.api.OpenListener;
import ch.prokopovi.ui.main.api.Opener;
import ch.prokopovi.ui.main.api.UpdateListener;
import ch.prokopovi.ui.main.api.Updater;

import com.adsdk.sdk.Ad;
import com.adsdk.sdk.AdListener;
import com.google.android.apps.analytics.GoogleAnalyticsTracker;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.googlecode.androidannotations.annotations.Bean;
import com.googlecode.androidannotations.annotations.EActivity;
import com.googlecode.androidannotations.annotations.Fullscreen;
import com.googlecode.androidannotations.annotations.NonConfigurationInstance;
import com.googlecode.androidannotations.annotations.OptionsItem;
import com.googlecode.androidannotations.annotations.OptionsMenu;
import com.googlecode.androidannotations.annotations.res.StringRes;

@EActivity(R.layout.fragment_tabs)
@OptionsMenu(R.menu.main_menu)
@Fullscreen
public class TabsActivity extends ActionBarActivity implements Updater, Opener,
		OpenListener, RateAppListener, AdListener {

	private static final long EXPIRATION_PERIOD = 10 * DateUtils.HOUR_IN_MILLIS;
	private static final int REGION_NEAR_THRESHOLD = 16 * 1000; // meters
	static final float DUAL_PANE_RATIO = 0.4f;

	private static final String LOG_TAG = "TabsActivity";

	static final DecimalFormat FMT_RATE_VALUE = new DecimalFormat("#.####");

	enum FragmentTag {
		BEST(BestRatesFragment_.class.getName(), R.id.main_container), //
		NEAR(NearFragment.class.getName(), R.id.main_container), //
		RATE(RateAppFragment_.class.getName(), R.id.bottom_container), BANNER(
				BannerFragment_.class.getName(), R.id.bottom_container);

		String tag;
		String className;
		int container;

		private FragmentTag(String clazz, int container) {
			this.tag = name();
			this.className = clazz;
			this.container = container;
		}
	}

	private ShareActionProvider mShareActionProvider;

	private ProgressDialog progressDialog;

	private BestRatesDbAdapter dbAdapter;

	private GoogleAnalyticsTracker tracker;

	private final Set<UpdateListener> updateListeners = new HashSet<UpdateListener>();

	private OpenListener openListener;

	private Region currentRegion;

	private boolean dualPane = false;

	private Location myLastLocation;

	private boolean locationIsUpdated = false;

	@StringRes(R.string.pref_rate_app_launches)
	String prefRateAppLaunches;

	@StringRes(R.string.pref_ads_on)
	String prefAdsOn;

	/*
	 * Using @NonConfigurationInstance on a @Bean will automatically update the
	 * context ref on configuration changes, if the bean is not a singleton
	 */
	@NonConfigurationInstance
	@Bean
	UpdateTask task;

	private final LocationListener locationListener = new LocationListener() {

		@Override
		public void onStatusChanged(String provider, int status, Bundle extras) {
		}

		@Override
		public void onProviderEnabled(String provider) {
		}

		@Override
		public void onProviderDisabled(String provider) {
		}

		@Override
		public void onLocationChanged(Location location) {
			Log.d(LOG_TAG, "onLocationUpdate: " + location);
			TabsActivity.this.myLastLocation = location;

			if (!TabsActivity.this.locationIsUpdated) {

				Region region = findRegion(location.getLatitude(),
						location.getLongitude());

				if (region != null) {
					// even if region is the same we call read() to
					// update data-location calculations
					Log.d(LOG_TAG, "setting region by location: " + region);

					TabsActivity.this.currentRegion = region;

					read(false);

					TabsActivity.this.locationIsUpdated = true;
				}
			} // set first time region

		}
	};

	/**
	 * create intent to launch main activity like from app launcher
	 * 
	 * @param c
	 * @return
	 */
	public static Intent getLauncherIntent(Context c) {

		Intent intent = new Intent(Intent.ACTION_MAIN);
		intent.setComponent(new ComponentName(c, TabsActivity_.class));
		intent.addCategory(Intent.CATEGORY_LAUNCHER);
		intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

		return intent;
	}

	@Override
	protected void onCreate(Bundle savedInstanceState) {

		// skin
		PrefsUtil.initSkin(this);

		super.onCreate(savedInstanceState);

		this.dualPane = isDualPane();

		ActionBar actionBar = getSupportActionBar();
		actionBar.setHomeButtonEnabled(true);
		actionBar.setDisplayShowTitleEnabled(this.dualPane);

		if (this.dualPane) {
			FragmentTransaction ft = getSupportFragmentManager()
					.beginTransaction();

			addOrAttachFragment(ft, this, FragmentTag.BEST);
			addOrAttachFragment(ft, this, FragmentTag.NEAR);

			ft.commit();

		} else {
			shiftFragments();
		}

		// DB ---
		DbHelper dbHelper = DbHelper.getInstance(this);
		SQLiteDatabase database = dbHelper.getDb();
		this.dbAdapter = new BestRatesDbAdapter(database);

		// tracker
		this.tracker = GoogleAnalyticsTracker.getInstance();
		this.tracker.setAnonymizeIp(true);

		this.tracker.startNewSession(StatsHelper.PROPERTY_ID, this);

		// rate me
		if (savedInstanceState == null) { // only freshly created activity

			SharedPreferences prefs = getSharedPreferences(
					PrefsUtil.PREFS_NAME, Context.MODE_PRIVATE);

			int launches = prefs.getInt(this.prefRateAppLaunches, 5); // launches
			// count
			Log.d(LOG_TAG, "rate app launches: " + launches);

			if (launches == 0) {

				FragmentManager fm = getSupportFragmentManager();

				FragmentTransaction ft = fm.beginTransaction();
				addOrAttachFragment(ft, this, FragmentTag.RATE);

				ft.commit();
			} else {

				if (launches > 0) {
					prefs.edit().putInt(this.prefRateAppLaunches, launches - 1)
							.commit();
				} // update count

				// ads if allowed
				if (prefs.getBoolean(this.prefAdsOn, true)) {
					FragmentManager fm = getSupportFragmentManager();

					FragmentTransaction ft = fm.beginTransaction();
					addOrAttachFragment(ft, this, FragmentTag.BANNER);

					ft.commit();
				} else {
					this.tracker.trackPageView("/adsOff");
				}
			}
		}

		// show progress dialog on orientation change
		if (this.task.isInProgress()) {
			getProgressDialog().show();
		}
	}

	/**
	 * ask user to switch location service on if needed
	 * 
	 * @param context
	 */
	private boolean askLocationIsOn() {

		final Context context = this;

		LocationManager locationManager = getLocationManager();
		String providerName = getProviderName();

		boolean providerEnabled = (providerName != null) ? locationManager
				.isProviderEnabled(providerName) : false;

		Log.d(LOG_TAG, "best location provider enabled: " + providerEnabled);
		if (!providerEnabled) {

			final SharedPreferences prefs = getSharedPreferences(
					PrefsUtil.PREFS_NAME, Context.MODE_PRIVATE);

			boolean isAskLocationAllowed = prefs.getBoolean(
					getString(R.string.pref_ask_location), true);

			Log.d(LOG_TAG, "ask location allowed: " + isAskLocationAllowed);
			if (isAskLocationAllowed) {

				new AlertDialog.Builder(context)
						.setTitle(R.string.no_location_svc_title)
						.setMessage(R.string.no_location_svc_msg)
						.setPositiveButton(R.string.yes,
								new DialogInterface.OnClickListener() {
									@Override
									public void onClick(DialogInterface dialog,
											int i) {

										context.startActivity(new Intent(
												android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS));

										dialog.dismiss();
									}
								})
						.setNeutralButton(R.string.no,
								new DialogInterface.OnClickListener() {
									@Override
									public void onClick(DialogInterface dialog,
											int which) {

										dialog.dismiss();
									}
								})
						.setNegativeButton(R.string.btn_do_not_ask,
								new DialogInterface.OnClickListener() {

									@Override
									public void onClick(DialogInterface dialog,
											int which) {

										// do not ask any more
										prefs.edit()
												.putBoolean(
														getString(R.string.pref_ask_location),
														false).commit();

										dialog.dismiss();
									}
								}).create().show();
			}
		}

		return providerEnabled;
	}

	private LocationManager getLocationManager() {

		LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

		return locationManager;
	}

	private String getProviderName() {

		Criteria locationCritera = new Criteria();
		locationCritera.setAccuracy(Criteria.ACCURACY_COARSE);
		locationCritera.setAltitudeRequired(false);
		locationCritera.setBearingRequired(false);
		locationCritera.setCostAllowed(true);
		locationCritera.setPowerRequirement(Criteria.NO_REQUIREMENT);

		String providerName = getLocationManager().getBestProvider(
				locationCritera, true);

		Log.d(LOG_TAG, "best location provider: " + providerName);

		return providerName;
	}

	/**
	 * search best last known location between all providers
	 * 
	 * @return
	 */
	private Location getLastKnownLocation() {
		LocationManager locationManager = getLocationManager();

		List<String> providers = locationManager.getAllProviders();
		Location bestLocation = null;
		for (String provider : providers) {
			Location loc = locationManager.getLastKnownLocation(provider);
			Log.d(LOG_TAG, "last known location, provider: " + provider
					+ ", location: " + loc);

			if (loc == null) {
				continue;
			}
			if (bestLocation == null
					|| loc.getAccuracy() < bestLocation.getAccuracy()) {
				Log.d(LOG_TAG, "found best last known location: " + loc);
				bestLocation = loc;
			}
		}

		return bestLocation;
	}

	/**
	 * get whether layout is dual-paned or single-paned
	 * 
	 * @return
	 */
	private boolean isDualPane() {

		int statusCode = GooglePlayServicesUtil
				.isGooglePlayServicesAvailable(this);
		boolean mapsAvailable = (statusCode == ConnectionResult.SUCCESS);
		Log.d(LOG_TAG, "maps available: " + mapsAvailable);

		Configuration cfg = getResources().getConfiguration();

		boolean isLandscape = cfg.orientation == Configuration.ORIENTATION_LANDSCAPE;
		Log.d(LOG_TAG, "is landscape: " + isLandscape);

		int masked = cfg.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK;
		boolean isLarge = masked == Configuration.SCREENLAYOUT_SIZE_LARGE
				|| VersionHelper.isXlarge(cfg);
		Log.d(LOG_TAG, "is large: " + isLarge);

		boolean res = mapsAvailable && isLandscape && isLarge;

		Log.d(LOG_TAG, "dual pane: " + res);

		return res;
	}

	/**
	 * find nearest region by location
	 * 
	 * @param inLat
	 *            latitude
	 * @param inLong
	 *            longitude
	 * 
	 * @return nearest region or null, if no regions in some radius
	 */
	static Region findRegion(double inLat, double inLng) {
		Region myRegion = null;

		float[] result = new float[1];

		float smallest = REGION_NEAR_THRESHOLD; // meters
		for (Region region : Region.values()) {

			double lat = region.getCoords().latitude;
			double lng = region.getCoords().longitude;

			Location.distanceBetween(inLat, inLng, lat, lng, result);

			// Log.d(LOG_TAG, "result: " + result[0]);
			if (smallest > result[0]) {
				smallest = result[0];
				myRegion = region;
				Log.d(LOG_TAG, "near region: " + region + ", dist: " + smallest);
			}
		}

		return myRegion;
	}

	@Override
	protected void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);
	}

	private void initLocation() {
		boolean locationIsOn = askLocationIsOn();

		if (locationIsOn) {

			if (this.myLastLocation == null)
				this.myLastLocation = getLastKnownLocation();

			getLocationManager().requestLocationUpdates(getProviderName(), 0,
					50, this.locationListener);
		}
	}

	@Override
	protected void onResume() {

		Log.d(LOG_TAG, "onResume");

		super.onResume();

		initLocation(); // location on/off

		this.dualPane = isDualPane(); // maps installed
	}

	@Override
	protected void onPause() {
		Log.d(LOG_TAG, "onPause");

		super.onPause();

		getLocationManager().removeUpdates(this.locationListener);
	}

	@Override
	public void onDestroy() {
		super.onDestroy();

		if (getProgressDialog().isShowing()) {
			getProgressDialog().dismiss();
		}

		this.tracker.dispatch();
		this.tracker.stopSession();

		Log.d(LOG_TAG, "closed");
	}

	void fireUpdate() {
		try {

			Log.d(LOG_TAG, "fire update for " + this.updateListeners.size()
					+ " listeners");

			for (UpdateListener listener : this.updateListeners) {

				listener.onUpdate();
			}

		} catch (Exception e) {
			Log.e(LOG_TAG, "error on fragments update", e);
		}
	}

	/**
	 * lazy progress dialog creating
	 * 
	 * @param c
	 * 
	 * @return
	 */
	ProgressDialog getProgressDialog() {
		if (this.progressDialog == null) {
			this.progressDialog = new ProgressDialog(this);
			this.progressDialog.setMessage("Обновляем ...");
			this.progressDialog.setCancelable(false);
		}

		return this.progressDialog;
	}

	@Override
	public void setOpenListener(OpenListener openListener) {
		this.openListener = openListener;
	}

	@Override
	public void onOpen(int id) {

		shiftFragments();

		if (this.openListener != null) {
			this.openListener.onOpen(id);
		}
	}

	/**
	 * check (basing on maxAge) whether web update needed or not
	 * 
	 * @param maxAge
	 * 
	 * @return true - if update needed, false - otherwise
	 */
	private boolean isLoadNeeded(Long maxAge) {

		Long now = new Date().getTime();
		Long lastUpdateTime = this.dbAdapter
				.fetchLastUpdateTime(this.currentRegion);

		Long age = now - lastUpdateTime;

		return (age > maxAge);
	}

	@Override
	public void read(boolean instantly) {

		boolean loadNeeded = isLoadNeeded(instantly ? 0 : EXPIRATION_PERIOD);
		if (loadNeeded) {

			if (!this.task.isInProgress()) {
				getProgressDialog().show();
				this.task.go(this.dbAdapter, this.currentRegion);
			} // already running
		} else {
			// skip update
			fireUpdate();
		}
	}

	@Override
	public GoogleAnalyticsTracker getTracker() {
		return this.tracker;
	}

	@Override
	public void addUpdateListener(UpdateListener updateListener) {
		this.updateListeners.add(updateListener);
	}

	@Override
	public void removeUpdateListener(UpdateListener updateListener) {
		this.updateListeners.remove(updateListener);
	}

	@Override
	public Location getLocation() {
		return this.myLastLocation;
	}

	@Override
	public Cursor getData(OperationType operationType,
			CurrencyCode currencyCode, int limit) {
		return this.dbAdapter.fetch(this.currentRegion, operationType,
				currencyCode, limit);
	}

	@Override
	public List<BestRatePlace> getData() {
		return this.dbAdapter.fetch(this.currentRegion);
	}

	@Override
	public void setRegion(Region region) {
		this.currentRegion = region;
	}

	@Override
	public Region getRegion() {
		return this.currentRegion;
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {

		// Locate MenuItem with ShareActionProvider
		MenuItem item = menu.findItem(R.id.menu_share_app);

		// Fetch and store ShareActionProvider
		this.mShareActionProvider = (ShareActionProvider) MenuItemCompat
				.getActionProvider(item);

		if (this.mShareActionProvider != null) {

			String appUri = "market://details?id=" + getPackageName();
			String appName = getResources().getString(R.string.app_name);

			Intent intentShare = new Intent(Intent.ACTION_SEND);
			intentShare.setType("text/plain");
			intentShare.putExtra(Intent.EXTRA_TEXT, appUri);
			intentShare.putExtra(android.content.Intent.EXTRA_SUBJECT, appName);

			this.mShareActionProvider.setShareIntent(intentShare);
		}

		return super.onCreateOptionsMenu(menu);
	}

	@Override
	public boolean onPrepareOptionsMenu(Menu menu) {

		// switch actions
		MenuItem bestItem = menu.findItem(R.id.menu_best);
		if (bestItem != null)
			bestItem.setVisible(!this.dualPane);

		MenuItem nearItem = menu.findItem(R.id.menu_near);
		if (nearItem != null)
			nearItem.setVisible(!this.dualPane);

		// rate-app action
		int launches = getSharedPreferences(PrefsUtil.PREFS_NAME,
				Context.MODE_PRIVATE).getInt(this.prefRateAppLaunches, 5);
		MenuItem rateAppItem = menu.findItem(R.id.menu_rate_app);
		if (rateAppItem != null) {
			rateAppItem.setVisible(launches >= 0);
		}

		return super.onPrepareOptionsMenu(menu);
	}

	@Override
	public void shiftFragments() {

		if (this.dualPane)
			return; // nothing to shift if dual-pane-layout

		FragmentManager fm = getSupportFragmentManager();

		Fragment best = fm.findFragmentByTag(FragmentTag.BEST.tag);

		boolean toBest = best == null || !best.isVisible();

		getSupportActionBar().setDisplayHomeAsUpEnabled(!toBest);

		//
		FragmentTransaction ft = fm.beginTransaction();

		if (toBest) {
			detachFragment(ft, FragmentTag.NEAR.tag);
			addOrAttachFragment(ft, this, FragmentTag.BEST);
		} else {
			detachFragment(ft, FragmentTag.BEST.tag);
			addOrAttachFragment(ft, this, FragmentTag.NEAR);
		}

		ft.commit();

		fm.executePendingTransactions();
	}

	/**
	 * add-new or attach-existing fragment
	 * 
	 * @param ft
	 *            transaction
	 * @param context
	 * @param info
	 *            fragment info
	 */
	private void addOrAttachFragment(FragmentTransaction ft, Context context,
			FragmentTag info) {
		FragmentManager fm = getSupportFragmentManager();
		Fragment fragment = fm.findFragmentByTag(info.tag);

		if (fragment == null) {
			fragment = Fragment.instantiate(context, info.className);
			ft.add(info.container, fragment, info.tag);
		} else {
			if (fragment.isDetached()) {
				ft.attach(fragment);
			}
		}
	}

	/**
	 * fragment detach-if-exists
	 * 
	 * @param ft
	 * @param tag
	 */
	private void detachFragment(FragmentTransaction ft, String tag) {
		FragmentManager fm = getSupportFragmentManager();
		Fragment fragment = fm.findFragmentByTag(tag);

		if (fragment != null) {
			ft.detach(fragment);
		}
	}

	/**
	 * fragment remove-if-exists in separate transaction
	 * 
	 * @param fTag
	 */
	private void removeFragment(FragmentTag fTag) {
		FragmentManager fm = getSupportFragmentManager();
		Fragment rateFragment = fm.findFragmentByTag(fTag.tag);
		if (rateFragment != null) {
			fm.beginTransaction().remove(rateFragment).commit();
		}
	}

	@Override
	public void onBackPressed() {

		Fragment best = getSupportFragmentManager().findFragmentByTag(
				FragmentTag.BEST.tag);
		if (best == null || !best.isVisible()) { // go main list
			shiftFragments();
		} else { // finish
			super.onBackPressed();
		}
	}

	@OptionsItem
	void menuRefresh() {
		Log.d(LOG_TAG, "updating...");

		this.tracker.trackPageView("/menuUpdate");

		read(true);
	}

	@OptionsItem
	void menuRateApp() {
		this.tracker.trackPageView("/menuRateApp");

		rateApp(this);

		afterRating(-1);
	}

	@OptionsItem
	void menuHelp() {
		this.tracker.trackPageView("/info");

		HelpActivity_.intent(this).start();
	}

	@OptionsItem
	void menuSettings() {
		this.tracker.trackPageView("/settings");

		PrefsActivity_.intent(this).start();
	}

	private static void rateApp(Context context) {

		String appUri = "market://details?id=" + context.getPackageName();

		Log.d(LOG_TAG, "rate app: " + appUri);

		Intent intentRateApp = new Intent(Intent.ACTION_VIEW, Uri.parse(appUri));
		context.startActivity(intentRateApp);
	}

	/**
	 * actions after rating choice is made
	 * 
	 * @param launches
	 *            new rate-app-launches property value
	 */
	private void afterRating(int launches) {

		if (launches < 0) {
			VersionHelper.invalidateOptionsMenu(this); // update action bar
			// items
		}

		// set property
		getSharedPreferences(PrefsUtil.PREFS_NAME, Context.MODE_PRIVATE).edit()
				.putInt(this.prefRateAppLaunches, launches).commit();

		// swith off dialog if exists
		removeFragment(FragmentTag.RATE);
	}

	@Override
	public void onRate(int buttonId) {

		int launches = 0;

		switch (buttonId) {
		case R.id.b_rate:
			this.tracker.trackPageView("/rated");

			rateApp(this);

			launches = -1;// no more asking

			break;
		case R.id.b_not_now:
			this.tracker.trackPageView("/rateNotNow");

			Log.d(LOG_TAG, "rate app action is postponed");

			launches = 3; // wait for 3 more launches
			break;
		case R.id.b_do_not_ask:
			this.tracker.trackPageView("/rateNever");

			Log.d(LOG_TAG, "rate app action is declined");

			launches = -1; // no more asking

			break;
		}

		afterRating(launches);
	}

	@Override
	public void adClicked() {
	}

	@Override
	public void adClosed(Ad arg0, boolean arg1) {
	}

	@Override
	public void adLoadSucceeded(Ad arg0) {
		Log.d(LOG_TAG, "ad load");
	}

	@Override
	public void adShown(Ad arg0, boolean arg1) {
		Log.d(LOG_TAG, "ad shown");
		this.tracker.trackPageView("/adShown");
	}

	@Override
	public void noAdFound() {
		Log.d(LOG_TAG, "no ad found");
		this.tracker.trackPageView("/noAdFound");

		removeFragment(FragmentTag.BANNER);
	}

}
